package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: huangYong
 * @Date: 2021/9/4 10:08
 */
public class HYApplicationContext {
   private Class clazz;
   private HashMap<String,BeanDefinition> beanDefinitionHashMap= new HashMap<>();
   //单例池
   private HashMap<String,Object> singletonObjects= new HashMap<>();

   //后置处理器BeanPostProcessor
    private List<BeanPostProcessor> postProcessors = new ArrayList<>();

    public HYApplicationContext(Class clazz) {
        //扫描类，生成bean定义
        scan(clazz);
        //创建bean
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionHashMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            BeanDefinition beanDefinition = beanDefinitionEntry.getValue();
            Object o =  createBean(beanName,beanDefinition);
            singletonObjects.put(beanName,o);
        }

    }




    private void scan(Class clazz) {
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
            String path = componentScan.value().replace(".","/");
            ClassLoader classLoader = HYApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File f : file.listFiles()) {
                    String absolutePath = f.getAbsolutePath();
                    String clazzPath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
                            .replaceAll("\\\\", ".");
                    try {
                        Class<?> aClass = classLoader.loadClass(clazzPath);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
                                try {
                                    BeanPostProcessor instance = (BeanPostProcessor) aClass.getConstructor().newInstance();
                                    postProcessors.add(instance);
                                } catch (InstantiationException e) {
                                    e.printStackTrace();
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                } catch (InvocationTargetException e) {
                                    e.printStackTrace();
                                } catch (NoSuchMethodException e) {
                                    e.printStackTrace();
                                }
                            }else {
                                Component component = aClass.getAnnotation(Component.class);
                                String beanName = component.value().isEmpty()? Introspector.decapitalize(aClass.getSimpleName()):component.value();
                                BeanDefinition beanDefinition = new BeanDefinition();
                                if (aClass.isAnnotationPresent(Scope.class)){
                                    Scope scope = aClass.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scope.value());
                                }else {
                                    beanDefinition.setScope("singleton");
                                }
                                beanDefinition.setClazz(aClass);
                                beanDefinitionHashMap.put(beanName,beanDefinition);
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        try {
            Class clazz = beanDefinition.getClazz();
            Object newInstance = clazz.getConstructor().newInstance();
            //依赖注入
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(newInstance,getBean(field.getName()));
                }
            }
            //初始化前方法执行
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(PostConstruct.class)) {
                    method.invoke(newInstance,method.getParameters());
                }
            }

            //初始化前执行beanPostProcessor - before
            for (BeanPostProcessor postProcessor : postProcessors) {
                newInstance =  postProcessor.postProcessBeforeInitialization(newInstance,beanName);
            }

            //初始化
            if (newInstance instanceof InitializingBean) {
                InitializingBean initializingBean = (InitializingBean)newInstance;
                initializingBean.afterPropertiesSet();
            }

            //初始化后执行beanPostProcessor -after
            for (BeanPostProcessor postProcessor : postProcessors) {
                newInstance =  postProcessor.postProcessAfterInitialization(newInstance,beanName);
            }


            return newInstance;

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Object getBean(String beanName) {
        if (!beanDefinitionHashMap.containsKey(beanName)) {
            throw new ClassCastException(beanName);
        }
        BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")) {
            Object o = singletonObjects.get(beanName);
            if (o == null){
                o = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,o);
            }
            return o;

        }else {
            Object bean = createBean(beanName, beanDefinition);
            return bean;
        }
    }
}
